OD Transport Data Visualization

Spatio-temporal data with OD data

This exercise is based on the tutorial “Analysing massive open human mobility data in R using spanishoddata, duckdb and flowmaps” by Egor Kotov.

This is a more advanced exercise that benefits from having a fast internet connection, decent compute resources, and an interest in the Iberian Peninsula.

See ekotov.pro for details.

Spanish OD Data

Example map creation

options(repos = c(CRAN = "https://cloud.r-project.org"))
packages <- c(
  "spanishoddata",
  "flowmapper",
  "flowmapblue",
  "tidyverse",
  "mapview",
  "leafgl",
  "patchwork",
  "sf",
  "basemaps",
  "ggnewscale"
)

install.packages(packages)

The downloaded binary packages are in
    /var/folders/g5/tzxh_kjx6tq7lqcgzp9mf4qm0000gn/T//RtmplHIYVI/downloaded_packages
rm(packages)
# This code chunk creates an interactive flow map for Seville
# demonstrating animation and time-filtering capabilities
# It is based on the vignette from the rOpenSpain/spanishoddata package.

# --- 1. Load necessary libraries ---
library(spanishoddata)
library(flowmapblue)
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.0.4     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(sf)
Linking to GEOS 3.13.0, GDAL 3.8.5, PROJ 9.5.1; sf_use_s2() is TRUE
# --- 2. Set up Mapbox Access Token (required for the basemap) ---
# Get a free token from https://account.mapbox.com/access-tokens/
Sys.setenv(MAPBOX_TOKEN = "pk.eyJ1IjoiZWxsYXN0dWJiaW5ncyIsImEiOiJjbWZxa2tvZTEwdGl2MmtyMmpuMnNpNzB2In0.O1IVyRdxM30sm60LFb6f1w")

# --- 3. Download and prepare the data ---

# Get OD data for a typical day in 2021
zones <- spod_get_zones(zones = "distr", ver = 2)
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Fetching latest metadata from AmazonS3 (v2)...
Cached new data to: /var/folders/g5/tzxh_kjx6tq7lqcgzp9mf4qm0000gn/T/RtmplHIYVI/metadata_cache/metadata_s3_v2_2025-09-19.rds
Downloading missing zones data...
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Fetching latest metadata from AmazonS3 (v1)...
Cached new data to: /var/folders/g5/tzxh_kjx6tq7lqcgzp9mf4qm0000gn/T/RtmplHIYVI/metadata_cache/metadata_s3_v1_2025-09-19.rds
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Deleting source `/var/folders/g5/tzxh_kjx6tq7lqcgzp9mf4qm0000gn/T/RtmplHIYVI/clean_data/v2/zones/distritos_mitma.gpkg' failed
Writing layer `distritos_mitma' to data source 
  `/var/folders/g5/tzxh_kjx6tq7lqcgzp9mf4qm0000gn/T/RtmplHIYVI/clean_data/v2/zones/distritos_mitma.gpkg' using driver `GPKG'
Writing 3909 features with 9 fields and geometry type Unknown (any).
valid_dates <- spod_get_valid_dates(2)
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
recent_dates = tail(valid_dates, 3)
flows <- spod_get(
  type = "origin-destination",
  zones = "districts",
  dates = recent_dates
)
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Warning: `time slot` was deprecated in spanishoddata 0.1.0.9000.
ℹ Please use `hour` instead.
ℹ `time_slot` column in origin destination data is now called `hour`.
  `time_slot` will be available in addition to `hour` and contain the same data
  in the outputs of `spod_get()` and `spod_convert()` until the end of 2025.
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Data version detected from dates: 2
Warning in spod_get_data_dir(): Warning: SPANISH_OD_DATA_DIR is not set. Using the temporary directory, which is not recommended, as the data will be deleted when the session ends.

 To set the data directory, use `spod_set_data_dir('/path/to/data')` or set SPANISH_OD_DATA_DIR permanently in the environment by editing the `.Renviron` file locally for current project with `usethis::edit_r_environ('project')` or `file.edit('.Renviron')` or globally for all projects with `usethis::edit_r_environ('user')` or `file.edit('~/.Renviron')`.
Using memory-cached available data from S3
Downloading approximately 0.67 GB of data.
Retrieved data for requested dates.
# --- 4. Process the OD data to create a timestamp for each flow ---
od_data_time <- flows |>
    mutate(time = as.POSIXct(paste0(date, "T", hour, ":00:00"))) |>
    group_by(origin = id_origin, dest = id_destination, time) |>
    summarise(count = sum(n_trips, na.rm = TRUE), .groups = "drop") |>
    collect()
# --- 5. Filter data for the Seville region ---
# Identify zones corresponding to Seville
zones_seville <- zones |>
    filter(grepl("^Sevilla distrito", name, ignore.case = TRUE))
    mapview::mapview(zones_seville)
# Create a 10km buffer to define the Functional Urban Area (FUA)
zones_seville_fua <- zones[st_buffer(zones_seville, dist = 10000), ]
plot(st_geometry(zones_seville_fua))

# Prepare the location data (centroids) for the flow map
sf::sf_use_s2(FALSE)
Spherical geometry (s2) switched off
locations_seville <- zones_seville_fua |>
    st_transform(crs = 4326) |>
    st_centroid() |>
    st_coordinates() |>
    as.data.frame() |>
    mutate(id = zones_seville_fua$id) |>
    rename(lon = X, lat = Y)
Warning: st_centroid assumes attributes are constant over geometries
Warning in st_centroid.sfc(st_geometry(x), of_largest_polygon =
of_largest_polygon): st_centroid does not give correct centroids for
longitude/latitude data
# Filter the time-based OD data to include only flows within the Seville
flows_seville_time <- od_data_time |>
    filter(origin %in% zones_seville_fua$id & dest %in% zones_seville_fua$id)

#Filter the time-based OD data to include only flows within the Seville FUA
flows_seville_time <- od_data_time |>
    filter(origin %in% zones_seville_fua$id & dest %in% zones_seville_fua$id)
# --- 6. Generate the interactive flow map ---
# Create the plot with animation and clustering enabled. 
# The resulting map will have a time slider to filter flows by hour.
flowmap_seville_interactive <- flowmapblue(
    locations = locations_seville,
    flows = flows_seville_time,
    mapboxAccessToken = Sys.setenv(MAPBOX_TOKEN = "pk.eyJ1IjoiZWxsYXN0dWJiaW5ncyIsImEiOiJjbWZxa2tvZTEwdGl2MmtyMmpuMnNpNzB2In0.O1IVyRdxM30sm60LFb6f1w"),
    darkMode = TRUE,
    animation = FALSE,
    clustering = TRUE
)

# Display the map
flowmap_seville_interactive